﻿using Nito.AsyncEx;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Runtime.Caching;
using System.Text;
using System.Threading.Tasks;

namespace MRSite.Cache
{
    public abstract class BaseCache : ICache
    {
        public string Name { get; }

        public TimeSpan SlidingExpireTime { get; set; }

        public TimeSpan? AbsoluteExpireTime { get; set; }

        public Collection<ChangeMonitor> ChangeMonitors { get; set; }

        protected BaseCache(string name)
        {
            Name = name;
            SlidingExpireTime = TimeSpan.FromHours(1);
            ChangeMonitors = new Collection<ChangeMonitor>();
        }

        public abstract void Clear();

        public virtual Task ClearAsync()
        {
            Clear();
            return Task.FromResult(0);
        }

        public virtual object Get(string key, Func<string, object> func)
        {
            var item = GetOrDefault(key);
            if (item is null)
            {
                lock (SyncObj)
                {
                    item = GetOrDefault(key);
                    if (item is null)
                    {
                        item = func(key);
                        if (item is null)
                            return null;
                        Set(key, item);
                    }
                }
            }
            return item;
        }

        public virtual async Task<object> GetAsync(string key, Func<string, Task<object>> func)
        {
            var cacheKey = key;
            var item = await GetOrDefaultAsync(key);
            if (item is null)
            {
                using (await _asyncLock.LockAsync())
                {
                    if (item is null)
                    {
                        item = await func(key);
                        if (item is null) return null;
                        await SetAsync(cacheKey, item);
                    }
                }
            }
            return item;
        }

        public abstract object GetOrDefault(string key);

        public Task<object> GetOrDefaultAsync(string key)
        {
            return Task.FromResult(GetOrDefault(key));
        }

        public abstract void Remove(string key);

        public Task RemoveAsync(string key)
        {
            Remove(key);
            return Task.FromResult(0);
        }

        public abstract void Set(string key, object value, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null);

        public Task SetAsync(string key, object value, TimeSpan? slidingExpireTime = null, TimeSpan? absoluteExpireTime = null)
        {
            Set(key, value, slidingExpireTime);
            return Task.FromResult(0);
        }

        public virtual void Dispose()
        {
        }

        protected readonly object SyncObj = new object();
        private readonly AsyncLock _asyncLock = new AsyncLock();
    }
}
